package biz

import (
	"context"
	"fmt"
	"github.com/gogf/gf/util/gconv"
	v1 "hellorockscache/api/helloworld/v1"
	"time"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
)

var (
	// ErrUserNotFound is user not found.
	ErrUserNotFound = errors.NotFound(v1.ErrorReason_USER_NOT_FOUND.String(), "user not found")
)

// Greeter is a Greeter model.
type Greeter struct {
	Hello string
}

// GreeterRepo is a Greater repo.
type GreeterRepo interface {
	// 正确使用的方式以及2个在实际中会遇到的 击穿、数据不一致的写法
	TestRocks(ctx context.Context, id int64) (*Whw, error)
	TestRocks1(ctx context.Context, id int64) error         // 击穿
	TestRocks2(ctx context.Context, id, change int64) error // 击穿+数据不一致

	// pipeline
	TestPipeline(ctx context.Context) error

	// 代码里面开并发测试 setNx 效果
	TestSetnx(ctx context.Context) error
	// MultiCheck 防并发处理,非重试的分布式锁
	// Notice uniqueLockKey 需要业务调用方自己生成!最好做到业务+用户维度,不要有不相关的业务与用户出现同一个uniqueKey的情况
	MultiCheck(ctx context.Context, uniqueLockKey string, lockTime time.Duration) (result bool)

	// 批量创建 测试唯一索引冲突
	BatchCreateStudentCourses(ctx context.Context, scs []*StudentCourse) error
}

// GreeterUsecase is a Greeter usecase.
type GreeterUsecase struct {
	repo     GreeterRepo
	log      *log.Helper
	junxuMap map[string]string
}

// NewGreeterUsecase new a Greeter usecase.
func NewGreeterUsecase(repo GreeterRepo, logger log.Logger, junxuMp map[string]string) *GreeterUsecase {
	return &GreeterUsecase{repo: repo, log: log.NewHelper(logger), junxuMap: junxuMp}
}

func (uc *GreeterUsecase) TestRocks(ctx context.Context, req *v1.TestRReq) (*v1.Ret, error) {
	reply := v1.Ret{}

	ret, err := uc.repo.TestRocks(ctx, req.Id)
	////Notice 需要做一下对 "防穿透空数据" 的判断～
	////Notice 这里只是为了演示，实际上有时候不会在biz层对错误进行判断而是在RPC调用方进行判断～
	//// 1、data层遇到空数据直接返回错误～
	//// 外层调用的话 err被封装了 这时候用字符串判断
	//if err == gorm.ErrRecordNotFound || strings.Contains(err.Error(), "record not found") {
	//	// TODO 相关业务处理～
	//	fmt.Println("data层返回了空数据!!")
	//}
	//// 判断其他error
	//if err != nil && !(err == gorm.ErrRecordNotFound || strings.Contains(err.Error(), "record not found")) {
	//	return nil, err
	//}

	// 2、data层遇到空数据返回"空结构体"
	//// 先判断err
	if err != nil {
		return nil, err
	}
	// Notice 数据库的id主键一定不为0
	if ret.Id == 0 {
		// TODO 业务处理～
		fmt.Println("data层返回了空数据!!!")
		//// 也可以不返回error
		//return nil, errors.New(450, fmt.Sprintf("id: %v 的数据不存在", req.Id), fmt.Sprintf("id: %v 的数据不存在", req.Id))
	}

	reply.Ret = gconv.String(ret)

	return &reply, nil
}

func (uc *GreeterUsecase) TestPipeline(ctx context.Context, req *v1.Empty) (*v1.Empty, error) {
	reply := &v1.Empty{}

	err := uc.repo.TestPipeline(ctx)
	if err != nil {
		return nil, err
	}

	return reply, nil
}

func (uc *GreeterUsecase) TestSetnx(ctx context.Context, req *v1.Empty) (*v1.Empty, error) {

	reply := &v1.Empty{}

	err := uc.repo.TestSetnx(ctx)
	if err != nil {
		return nil, err
	}

	return reply, nil
}

func (uc *GreeterUsecase) TestMultiCheck(ctx context.Context, req *v1.Empty) (*v1.Empty, error) {

	fmt.Println("junxumap: ", uc.junxuMap)

	reply := &v1.Empty{}

	// Notice 自己根据业务生成redisKey
	uniqueLockKey := fmt.Sprintf("xxx:123:666")
	// Notice 将有可能并发调用的代码写在里面
	// Notice 并发情况下,同一个uniqueLockKey下面的逻辑只会执行一次～
	// Notice uniqueLockKey 的TTL值根据实际业务情况确定～我这里固定设置成了5秒
	if uc.repo.MultiCheck(ctx, uniqueLockKey, time.Second*5) {

		fmt.Println("有可能出现并发调用的业务逻辑...请求RPC/HTTP等...写在这里")
	}

	return reply, nil
}

/*
// Notice 作者业务中遇到的 使用 redis分布式锁的业务代码片段,了解大概逻辑就行
func (uc *ActivityBiz) AutoReceiveReward(ctx context.Context, msg *mq.RewardEvent) (err error) {

	if msg == nil || msg.EventData == nil || len(msg.EventData) < 1 {
		return
	}

	// Notice 使用fx并发处理
	fx.From(func(source chan<- interface{}) {
		for _, event := range msg.EventData {
			source <- event
		}
	}).Parallel(func(item interface{}) {
		event := item.(*mq.EventData)
		if event == nil {
			return
		}
		// Notice 防止并发
		lockKey := fmt.Sprintf("auto_receive_lock:%v:%v:%v:%v", event.Uid, event.ActivityId, event.MissionId, event.MissionTimes)
		if uc.Repo.MutCheck(ctx, lockKey, time.Minute) {
			req := &pb.UserReceiveRewardReq{
				SteamId:    event.SteamId,
				ActivityId: event.ActivityId,
				MissionIds: []int64{event.MissionId},
				UserId:     event.Uid,
			}
			uc.Log.WithContext(ctx).Infow("auto_receive_req:", req, "event:", event)
			_, err = uc.UserReceiveMultiReward(ctx, req, utils.SendTypeAuto)
			if err != nil {
				if strings.Contains(err.Error(), "已领取过~") {
					err = nil
				}
				uc.Log.WithContext(ctx).Errorw("auto_receive_error:", err)
			}
		}
	})
	return
}
*/

func (uc *GreeterUsecase) TestBatchCreateUniqueIdx(ctx context.Context, req *v1.TestBatchCreateUniqueIdxReq) (*v1.Empty, error) {

	reply := &v1.Empty{}

	s1 := make([]*StudentCourse, 0)

	if len(req.CourseIds) != len(req.StudentIds) {
		return nil, errors.New(450, "长度不一致", "长度不一致")
	}

	for index, sid := range req.StudentIds {
		currS := &StudentCourse{
			StudentId: sid,
			CourseId:  req.CourseIds[index],
		}
		s1 = append(s1, currS)
	}

	err := uc.repo.BatchCreateStudentCourses(ctx, s1)
	if err != nil {
		return nil, err
	}

	return reply, nil

}
